﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ETRC.CommLib
{
    using System.IO.Ports;
    using System.Threading;

    public class DXComPortManager: IDataExchangeManager
    {
        private SerialPort currentPort;

        public System.IO.Ports.SerialPort CurrentPort
        {
            get { return currentPort; }
        }

        private CommLogWriter logWriter;
        public CommLogWriter LogWriter
        {
            get
            {
                lock (this)
                {
                    return logWriter;
                }
            }
            set
            {
                lock (this)
                {
                    logWriter = value;
                }
            }
        }

        public DXComPortManager()
        {
            currentPort = new SerialPort();
        }

        private Thread myThread;
        private ManualResetEvent asyncReceiveEvent = new ManualResetEvent(false);
        private ManualResetEvent syncReceiveEvent = new ManualResetEvent(false);
        public void Start()
        {
            if (currentPort.IsOpen)
            {
                myThread = new Thread(ReceiveWorkThreadFunc);
                myThread.Start(this);
            }
        }

        public void Stop()
        {
            currentPort.Close();
            asyncReceiveEvent.Set();
        }

        private static void ReceiveWorkThreadFunc(object target)
        {
            DXComPortManager my = target as DXComPortManager;
            my.ReceiveWork();
        }

        private bool isCurrentSyncing=false;
        public void ReceiveWork()
        {
            try
            {
                currentPort.DataReceived += new SerialDataReceivedEventHandler(currentPort_DataReceived);
                while (true)
                {
                    asyncReceiveEvent.Reset();
                    // 仮想COMポートにデータが受信されてcurrentPort_DataReceivedがコールされて
                    // そこで、asyncReceiveEventに対しSet()メソッドがコールされるとWaitOneブロックを抜ける
                    asyncReceiveEvent.WaitOne();
                    if (currentPort.IsOpen)
                    {
                        byte[] buf = null;
                        lock (this)
                        {
                            buf = ReceiveDataUnit();
                        }
                        lock (receivedData)
                        {
                            if (buf != null)
                            {
                                receivedData.Add(buf);
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {

            }

        }

        private byte[] ReceiveDataUnit()
        {
            byte[] buf = new byte[bytesToRead];
            int readBytes = 0;
            while (readBytes < bytesToRead)
            {
                int bytes = currentPort.Read(buf, readBytes, bytesToRead - readBytes);
                readBytes += bytes;
            }
            return buf;
        }

        private int bytesToRead = 0;
        void currentPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            lock (this)
            {
                bytesToRead = currentPort.BytesToRead;
                if (isCurrentSyncing)
                {
                    syncReceiveEvent.Set();
                }
                else
                {
                    asyncReceiveEvent.Set();
                }
            }
        }

        private List<byte[]> receivedData = new List<byte[]>();

        public byte[] GetReceiveData()
        {
            byte[] rdata = null;
            lock (receivedData)
            {
                if (receivedData.Count > 0)
                {
                    rdata = receivedData[0];
                    receivedData.RemoveAt(0);
                }
            }
            return rdata;
        }

        public void SendData(byte[] buf, int offset, int count)
        {
            currentPort.Write(buf, offset, count);
        }

        public void SendData(string data)
        {
            currentPort.WriteLine(data);
        }

        public void SendSyncData(byte[] buf, int offset, int count, List<byte[]> response, ResponseDataResolver resolver = null)
        {
            syncReceiveEvent.Reset();
            lock (this)
            {
                isCurrentSyncing = true;
                SendData(buf, offset, count);
            }
            if (resolver != null)
            {
                ResolveSyncResponse(response, resolver);
            }
            lock (this)
            {
                isCurrentSyncing = false;
            }
        }

        private void ResolveSyncResponse(List<byte[]> response, ResponseDataResolver resolver)
        {
            bool isDone = false;
            while (!isDone)
            {
                syncReceiveEvent.WaitOne();
                byte[] rdata = ReceiveDataUnit();
                bool isResponse;
                resolver.IsValidResponse(rdata, out isResponse, out isDone);
                if (isResponse)
                {
                    response.Add(rdata);
                }
                else
                {
                    lock (receivedData)
                    {
                        receivedData.Add(rdata);
                    }
                }
            }
        }

        public void SendSyncData(string buf, List<byte[]> response, ResponseDataResolver resolver = null)
        {
            syncReceiveEvent.Reset();
            lock (this)
            {
                isCurrentSyncing = true;
                SendData(buf);
            }
            if (resolver != null)
            {
                ResolveSyncResponse(response, resolver);
            }
            lock (this)
            {
                isCurrentSyncing = false;
            }
        }
    }
}
